Hallo zusammen,
ich habe nun seit längerem ein Problem mit oben genannter, teils selbst entwickelter, Funktionalität.
Zu den Rahmenbedingungen: TYPO3 v12, georgringer/news in der aktuellen Version und eine Abwandlung der ext-news/news_like Extension. Diese Extension wurde von mir von jQuery auf Plain-JS umgeschrieben und aktuell findet auch keine Hash-Validierung mehr statt.
Mein Problem ist, dass wenn ein Redakteur einen News Beitrag veröffentlicht, egal ob über Workspaces oder direkt, die Like Funktion mal funktioniert und mal nicht. Ich kann aber nicht nachvollziehen wieso.
Im Grunde ist der Ablauf simpel:
- Beitrag wird in der Detailansicht geöffnet und ein Eintrag wird in einer Datenbank Tabelle angelegt mit FE-User ID und News ID
- Es werden alle Likes aus der Tabelle zu dem Artikel zusammengezählt und in einem Button angezeigt
2.5 Ist die ID des FE-Users in der Liste der gesammelten Likes bereits enthalten, wird der Button zusätzlich deaktiviert
- Klickt der User auf den Button um einen Like zu vergeben wird ein Parameter in der DB auf true gesetzt und die Zahl im Button um 1 erhöht.
Ich beobachte sehr oft, dass ein User eine Detailansicht aufruft und dort auch die "richtige" zahl der Likes im Button angezeigt wird (richtig heißt hier, alle Likes die in der DB gefunden werden konnten, also wo keine Probleme aufgetreten sind, es mag sein, dass es in Wirklichkeit mehr Likes gegeben hätte). Klickt der User nun auf den Button wird dieser deaktiviert, so wie es auch geschehen soll, die Zahl im Button erhöht sich allerdings nicht und auch in der Datenbank wird der Artikel nicht als geliked makiert.
Ich stelle euch hier mal meinen code zur Verfügung:
document.addEventListener('DOMContentLoaded', function () {
const firstNewsElement = document.querySelector('.news-like-js');
if (!firstNewsElement) return;
const newsId = firstNewsElement.getAttribute('data-news');
document.querySelectorAll('.news-like-js').forEach(function (element) {
console.log(element);
element.addEventListener('click', function () {
// AJAX Request
const xhr = new XMLHttpRequest();
xhr.open('POST', '/tx_fmonews_like', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
if (Object.keys(data).length > 0) {
const text = document.querySelector('.news-like-text-js');
text.style.display = 'block';
text.querySelector('span').textContent = data.count;
document.querySelectorAll('.news-like-js').forEach(function (el) {
el.classList.add('disabled');
});
}
}
};
xhr.send(JSON.stringify({
news: newsId,
action: 'count',
}));
});
});
// AJAX Request
const xhr = new XMLHttpRequest();
xhr.open('POST', '/tx_fmonews_like', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
if (Object.keys(data).length > 0) {
const text = document.querySelector('.news-like-text-js');
text.style.display = 'block';
text.querySelector('span').textContent = data.count;
if (!data.allowed) {
document.querySelectorAll('.news-like-js').forEach(function (el) {
el.classList.add('disabled');
});
}
}
}
};
xhr.send(JSON.stringify({
news: newsId,
action: 'data',
}));
});
<?php
namespace Fmo\FmoNews\Middleware;
use JsonException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
class AjaxMiddleware implements MiddlewareInterface
{
const LOG_TABLE = 'tx_fmonews_feusers_news_mm';
public function __construct(
private readonly ConnectionPool $connectionPool,
) {}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
): ResponseInterface {
if ($request->getRequestTarget() === '/tx_fmonews_like') {
$inputJSON = file_get_contents('php://input');
try {
$data = json_decode($inputJSON, TRUE, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
return $handler->handle($request);
}
$newsId = (int)($data['news'] ?? 0);
$feUserId = $this->getFeuserId($request);
if (!$newsId || !$feUserId) {
return new JsonResponse([]);
}
if (!($data['action'] ?? false)) {
return new JsonResponse([]);
}
$response = [];
switch ($data['action']) {
case 'count':
$this->countNewsLike($newsId, $feUserId);
$response['count'] = $this->getLikeCount($newsId);
break;
case 'data':
$response['count'] = $this->getLikeCount($newsId);
$response['allowed'] = $this->isUserAllowedToVote($newsId, $feUserId);
break;
default:
throw new \UnexpectedValueException('No action set');
}
return new JsonResponse($response);
}
return $handler->handle($request);
}
private function getLikeCount(int $newsId): int {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::LOG_TABLE);
$count = $queryBuilder
->count('has_liked')
->from(self::LOG_TABLE)
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($newsId, Connection::PARAM_INT)),
$queryBuilder->expr()->eq('has_liked', $queryBuilder->createNamedParameter(1, Connection::PARAM_INT)),
)
->executeQuery()
->fetchOne();
return $count;
}
private function countNewsLike(int $newsId, int $feUserId): void {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::LOG_TABLE);
$queryBuilder
->update(self::LOG_TABLE)
->set('has_liked', 1)
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($newsId, Connection::PARAM_INT)),
$queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter($feUserId, Connection::PARAM_INT)),
$queryBuilder->expr()->eq('has_liked', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
)
->executeStatement();
}
private function getFeuserId(RequestInterface $request): int {
$frontendUser = $request->getAttribute('frontend.user');
if ($frontendUser instanceof FrontendUserAuthentication) {
return $frontendUser->getUserId();
}
return 0;
}
private function isUserAllowedToVote(int $newsId, int $feUserId): bool {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::LOG_TABLE);
$count = $queryBuilder
->count('has_liked')
->from(self::LOG_TABLE)
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($newsId, Connection::PARAM_INT)),
$queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter($feUserId, Connection::PARAM_INT)),
$queryBuilder->expr()->eq('has_liked', $queryBuilder->createNamedParameter(1, Connection::PARAM_INT)),
)
->executeQuery()
->fetchOne();
return $count === 0;
}
}
Vielen Dank für jeden Tipp der mich der Lösung einen Schritt näher bringt!
Johannes